[BUUCTF]CrackRTF

无壳, 拖进IDA, F5查看伪代码

可以看到两个关键函数

image-20220126164811383

两个函数结构区别不大, 里面调用了很多WidnwosCryptoAPI

这里以sub_40100A()为例, 通过它间接调用sub_401230()

查询微软文档, CryptCreateHash()函数的第二个参数指明算法类型

image-20220126165402916

查表可以得知这个函数是进行SHA-1哈希

微软文档: MSDN

image-20220126170805262

同理可以得知sub_401019()是进行MD5哈希的函数

通过这一段可以得知第一段密码长度是6位, 且为6位数字, 可以直接穷举

1
2
3
4
5
6
7
8
if ( strlen(Destination) != 6 )
{
printf("Must be 6 characters!\n");
ExitProcess(0);
}
v7 = atoi(Destination);
if ( v7 < 100000 )
ExitProcess(0);

代码如下

1
2
3
4
5
6
7
8
9
import hashlib

for i in range(100001,1000000):
hasher = hashlib.sha1()
hasher.update((str(i)+"@DBApp").encode("utf-8"))
if hasher.hexdigest().upper() == "6E32D0943418C2C33385BC35A1470250DD8923A9":
print(i)
break
#123321

所以第一段密码就是123321

第二段密码, 同样长度为6位, 但是第二段密码并没有限制是纯数字.

第二次验证的算法改成了MD5, 进行哈希的内容就是第二段密码+第一段密码+@DBApp组成的18字节字符.

方法一: 真·暴力破解法

所有ASCII可打印字符从32~126共95个, 第二段密码长6位, 那么可能性就有\(95^6\)种, 大约7350亿种可能, 写脚本穷举好像不太可能...但是, 显卡进行MD5哈希的速度可是相当快的, CPU则要慢得多多多多多...

放张hashcatbenchmark感受一下, 这还只是个3050Ti, 换做高端一点的那更是快得多.image-20220126193440960

换算出来差不多就是164亿次哈希每秒, 照这个速度7350亿种可能也不是很多.

确定可行性之后, 就可以用hashcat来进行穷举

hashcat: Github

N卡安装好CUDA套件之后, 在hashcat目录下打开命令行执行

1
hashcat -a 3 27019e688a4e62a649fd99cadaafdb4e -m 0 ?a?a?a?a?a?a123321@DBApp

-a 3 指定攻击方式, 掩码攻击

-m 0 指定哈希类型, MD5

?a?a?a?a?a?a123321@DBApp 是掩码, 前面6位代表所有可打印ASCII字符

实际的哈希速度要慢一些, 但依然是极快的, 不到半分钟就完成了工作

image-20220126195352794

得出第二段密码是~!3a@0

方法二: 常规方法

老实说我是上来就考虑用hashcat尝试穷举的, 没想到还有方法, 看了别人的WP才知道.

关键点在这个函数image-20220126203837733

跟进去找到sub_4014D0()这个函数, WindowsAPI有点多, 结合微软的文档分析其用意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
char __cdecl sub_4014D0(LPCSTR lpString)
{
LPCVOID lpBuffer; // [esp+50h] [ebp-1Ch]
DWORD NumberOfBytesWritten; // [esp+58h] [ebp-14h] BYREF
DWORD nNumberOfBytesToWrite; // [esp+5Ch] [ebp-10h]
HGLOBAL hResData; // [esp+60h] [ebp-Ch]
HRSRC hResInfo; // [esp+64h] [ebp-8h]
HANDLE hFile; // [esp+68h] [ebp-4h]

hFile = 0;
hResData = 0;
nNumberOfBytesToWrite = 0;
NumberOfBytesWritten = 0;
hResInfo = FindResourceA(0, (LPCSTR)0x65, "AAA");
//从载入为当前进程的PE文件,找到一个名为"e"(0x65)且资源类型为"AAA"的资源
if ( !hResInfo )
return 0;
nNumberOfBytesToWrite = SizeofResource(0, hResInfo);
//计算资源大小
hResData = LoadResource(0, hResInfo);
//载入资源
if ( !hResData )
return 0;
lpBuffer = LockResource(hResData);
//锁定资源,返回资源在内存中的指针
sub_401005(lpString, (int)lpBuffer, nNumberOfBytesToWrite);
//下一个关注点,此函数处理资源,对其进行异或解密
hFile = CreateFileA("dbapp.rtf", 0x10000000u, 0, 0, 2u, 0x80u, 0);
//创建名为"dbapp.rtf"的文件,储存解密后的rtf格式flag文件
if ( hFile == (HANDLE)-1 )
return 0;
if ( !WriteFile(hFile, lpBuffer, nNumberOfBytesToWrite, &NumberOfBytesWritten, 0) )
//将解密后的资源写入到文件中
return 0;
CloseHandle(hFile);
return 1;
}

sub_401005()调用了sub_401420(), 接着分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
unsigned int __cdecl sub_401420(LPCSTR lpString, int a2, int a3)
{
unsigned int result; // eax
unsigned int i; // [esp+4Ch] [ebp-Ch]
unsigned int v5; // [esp+54h] [ebp-4h]

v5 = lstrlenA(lpString);
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(i + a2) ^= lpString[i % v5];
//循环异或
}
return result;
}

这个函数将资源文件每个字节与第二段密码+第一段密码+@DBApp组成密钥进行循环异或从而解密出flag文件. 突破点就在flag文件的前6个字节上, 要知道, RTF格式不像txt这种文本格式, 它是有文件头的, 而文件头往往是固定的! 既然flag是个RTF格式的文件, 那么它一定符合RTF的文件头规范. 我们可以通过这已知的6个字节, 倒推出密钥前6字节, 即第二段密码.

找个RTF文件看看前面6字节

image-20220126211735455

前6字节是0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31

写出解密代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <cstdio>
#include <Windows.h>

using namespace std;

int main()
{
HMODULE hModule = LoadLibraryA("crack_rtf.exe");
//将程序载入获取模块句柄
if (!hModule)
return -1;
HRSRC hResInfo = FindResourceA(hModule, (LPCSTR)0x65, "AAA");
if (!hResInfo)
return -1;
HGLOBAL hResData = LoadResource(hModule, hResInfo);
if (!hResData)
return -1;
BYTE* lpRes = (BYTE*)LockResource(hResData);
if (!lpRes)
return -1;
BYTE lpHeaderByte[6] = { 0x7B, 0x5C, 0x72, 0x74, 0x66, 0x31 };
//RTF文件头前6字节
for (DWORD i = 0; i < 6; i++)
for (BYTE c = 32; c < 127; c++) //穷举所有ASCII可打印字符
if ((lpRes[i] ^ c) == lpHeaderByte[i]) //验证字符
putchar(c);
}
//~!3a@0

最终, flagflag{N0_M0re_Free_Bugs}